﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.UI;
using PmfWeb.Core.Payment.Result;

namespace PmfWeb.Core.Payment
{
    ///<summary>
    /// 支付宝充值类
    ///</summary>
    public class AliPay
    {
        #region 事件
        /// <summary>
        /// 事件类
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public delegate void ProcessNotifyEventHandler(object sender, NotifyEventArgs e);

        /// <summary>
        /// 等待买家付款
        /// </summary>
        public event ProcessNotifyEventHandler WaitBuyerPay;
        /// <summary>
        /// 交易已创建，等待卖家确认
        /// </summary>
        public event ProcessNotifyEventHandler WaitSellerConfirmTrade;
        /// <summary>
        /// 确认买家付款中，暂勿发货
        /// </summary>
        public event ProcessNotifyEventHandler WaitSysConfirmPay;
        /// <summary>
        /// 支付宝收到买家付款，请卖家发货
        /// </summary>
        public event ProcessNotifyEventHandler WaitSellerSendGoods;
        /// <summary>
        /// 确认买家付款中，暂勿发货
        /// </summary>
        public event ProcessNotifyEventHandler WaitBuyerConfirmGoods;
        /// <summary>
        /// 买家确认收到货，等待支付宝打款给卖家
        /// </summary>
        public event ProcessNotifyEventHandler WaitSysPaySeller;
        /// <summary>
        /// 交易成功结束
        /// </summary>
        public event ProcessNotifyEventHandler TradeFinished;
        /// <summary>
        /// 交易中途关闭（未完成）
        /// </summary>
        public event ProcessNotifyEventHandler TradeClosed;

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnWaitSellerSendGoods(NotifyEventArgs e)
        {
            if (WaitSellerSendGoods != null)
            {
                WaitSellerSendGoods(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnWaitBuyerPay(NotifyEventArgs e)
        {
            if (WaitBuyerPay != null)
            {
                WaitBuyerPay(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnWaitSysConfirmPay(NotifyEventArgs e)
        {
            if (WaitSysConfirmPay != null)
            {
                WaitSysConfirmPay(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnWaitSellerConfirmTrade(NotifyEventArgs e)
        {
            if (WaitSellerConfirmTrade != null)
            {
                WaitSellerConfirmTrade(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnWaitBuyerConfirmGoods(NotifyEventArgs e)
        {
            if (WaitBuyerConfirmGoods != null)
            {
                WaitBuyerConfirmGoods(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnWaitSysPaySeller(NotifyEventArgs e)
        {
            if (WaitSysPaySeller != null)
            {
                WaitSysPaySeller(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnTradeFinished(NotifyEventArgs e)
        {
            if (TradeFinished != null)
            {
                TradeFinished(this, e);
            }
        }

        ///<summary>
        /// 
        ///</summary>
        ///<param name="e"></param>
        public virtual void OnTradeClosed(NotifyEventArgs e)
        {
            if (TradeClosed != null)
            {
                TradeClosed(this, e);
            }
        }
        #endregion
        #region Public
        /// <summary>
        /// 创建虚拟交易
        /// </summary>
        /// <param name="gatewayUrl">提交支付宝的地址</param>
        /// <param name="digitalGoods">交易参数</param>
        /// <param name="page">Page对象</param>
        public void CreateDigitalTrade(string gatewayUrl, DigitalGoods digitalGoods, Page page)
        {
            HttpResponse response = page.Response;
            response.Redirect(gatewayUrl + "?" + Md5SignParam(digitalGoods));

        }
        /// <summary>
        /// 创建标准交易，包含虚拟交易
        /// </summary>
        /// <param name="gatewayUrl">提交支付宝的地址</param>
        /// <param name="standardGoods">交易参数</param>
        /// <param name="page">Page对象</param>
        public void CreateStandardTrade(string gatewayUrl, StandardGoods standardGoods, Page page)
        {
            HttpResponse response = page.Response;
            response.Redirect(gatewayUrl + "?" + Md5SignParam(standardGoods));
        }

        /// <summary>
        /// 处理返回的Notify
        /// </summary>
        /// <param name="page">传如Page对象</param>
        /// <param name="verifyUrl">验证的地址，如：https://www.alipay.com/cooperate/gateway.do</param>
        /// <param name="key">账户的交易安全校验码（key）</param>
        /// <param name="verify">verify对象</param>
        /// <param name="encode">编码</param>
        /// <exception cref="自定义异常">AliPayBaseException</exception>
        public void ProcessNotify(Page page, string verifyUrl, string key, Verify verify, string encode)
        {
            if (VerifyNotify(verifyUrl, verify))  //验证成功
            {
                NotifyEventArgs dn = new NotifyEventArgs();
                dn = ParseNotify(page.Request.Form, dn);//构造事件参数
                SortedList<string, string> sortedList = GetParam(dn);
                string param = GetUrlParam(sortedList, false);
                string sign = GetMd5Sign(encode, param + key);
                if (sign == dn.Sign)//验证签名
                {
                    try
                    {
                        if (dn.Trade_Status == "WAIT_SELLER_SEND_GOODS")//触发事件
                        {
                            OnWaitSellerSendGoods(dn);
                        }
                        else if (dn.Trade_Status == "WAIT_BUYER_PAY")
                        {
                            OnWaitBuyerPay(dn);
                        }
                        else if (dn.Trade_Status == "WAIT_SELLER_CONFIRM_TRADE")
                        {
                            OnWaitSellerConfirmTrade(dn);
                        }
                        else if (dn.Trade_Status == "WAIT_SYS_CONFIRM_PAY")
                        {
                            OnWaitSysConfirmPay(dn);
                        }
                        else if (dn.Trade_Status == "WAIT_BUYER_CONFIRM_GOODS")
                        {
                            OnWaitBuyerConfirmGoods(dn);

                        }
                        else if (dn.Trade_Status == "WAIT_SYS_PAY_SELLER")
                        {
                            OnWaitSysPaySeller(dn);
                        }
                        else if (dn.Trade_Status == "TRADE_FINISHED")
                        {
                            OnTradeFinished(dn);
                        }
                        else if (dn.Trade_Status == "TRADE_CLOSED")
                        {
                            OnTradeClosed(dn);
                        }
                        page.Response.Write("success");
                    }
                    catch (Exception e)
                    {
                        //支付宝如果是没有收到success就一直发Notify消息，则可以省略此try块
                        page.Response.Write("fail");
                        throw e;
                    }
                }
                else
                {
                    //支付宝如果是没有收到success就一直发Notify消息，则可以省略发Fail消息
                    page.Response.Write("fail");
                    throw new AliPayBaseException("支付宝通知签名验证失败", 101);
                }
            }
            else
            {
                //支付宝如果是没有收到success就一直发Notify消息，则可以省略发Fail消息
                page.Response.Write("fail");
                throw new AliPayBaseException("支付宝通知验证失败", 101);
            }

        }
        #endregion

        #region private
        /// <summary>
        /// 通知验证接口
        /// </summary>
        /// <param name="verifyUrl"></param>
        /// <param name="verify">验证参数</param>
        /// <returns>true，通过验证</returns>
        private bool VerifyNotify(string verifyUrl, Verify verify)
        {
            //#if (DEBUG)
            //            {
            //                return true;
            //            }
            //#endif
            string param;
            SortedList<string, string> sl = GetParam(verify);
            param = GetUrlParam(sl, true);
            string result = PostData(verifyUrl, param, "utf-8");
            //解析result
            return bool.Parse(result); ;
        }

        /// <summary>
        ///  解析Form集合到DigitalNotifyEventArgs,值类型会被初始化为null
        /// </summary>
        /// <param name="nv"></param>
        /// <param name="obj"></param>
        /// <remarks>为防止值类型的默认值污染参数集合,用了nullable范型</remarks>
        private NotifyEventArgs ParseNotify(NameValueCollection nv, object obj)
        {
            PropertyInfo[] propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (PropertyInfo pi in propertyInfos)
            {
                string v = nv.Get(pi.Name.ToLower());
                if (v != null)
                {
                    if (pi.PropertyType == typeof(string))
                    {

                        pi.SetValue(obj, v, null);

                    }
                    else if (pi.PropertyType == typeof(int?))
                    {
                        pi.SetValue(obj, int.Parse(v), null);
                    }
                    else if (pi.PropertyType == typeof(decimal?))
                    {

                        pi.SetValue(obj, decimal.Parse(v), null);
                    }
                    else if (pi.PropertyType == typeof(DateTime?))
                    {

                        pi.SetValue(obj, DateTime.Parse(v), null);
                    }
                    else if (pi.PropertyType == typeof(bool))
                    {

                        pi.SetValue(obj, bool.Parse(v), null);
                    }
                    else
                    {
                        //转型失败会抛出异常
                        pi.SetValue(obj, v, null);
                    }
                }

            }
            return (NotifyEventArgs)obj;

        }
        /// <summary>
        /// 获取Md5sign后的参数
        /// </summary>
        /// <param name="digitalGoods"></param>
        /// <returns></returns>
        private string Md5SignParam(DigitalGoods digitalGoods)
        {
            if (digitalGoods.Sign_Type.ToLower() != "md5")
            {
                throw new AliPayBaseException("Sign_Type不支持MD5", 100);
            }

            SortedList<string, string> goods = GetParam(digitalGoods);

            string param = GetUrlParam(goods, false) + digitalGoods.Sign;
            string encodeParam = GetUrlParam(goods, true) + "&";
            string sign = GetMd5Sign(digitalGoods._Input_Charset, param);
            return encodeParam + string.Format("sign={0}&sign_type={1}", HttpUtility.HtmlEncode(sign),
                HttpUtility.HtmlEncode(digitalGoods.Sign_Type));
        }

        /// <summary>
        /// 获取排序后的参数
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private SortedList<string, string> GetParam(object obj)
        {

            PropertyInfo[] propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            SortedList<string, string> sortedList = new SortedList<string, string>(StringComparer.CurrentCultureIgnoreCase);
            foreach (PropertyInfo pi in propertyInfos)
            {
                if (pi.GetValue(obj, null) != null)
                {
                    if (pi.Name == "Sign" || pi.Name == "Sign_Type")
                    {
                        continue;
                    }
                    sortedList.Add(pi.Name.ToLower(), pi.GetValue(obj, null).ToString());

                }
            }
            return sortedList;

        }



        /// <summary>
        /// 获取Url的参数
        /// </summary>
        /// <param name="sortedList"></param>
        /// <param name="isEncode">参数是否经过编码,被签名的参数不用编码,Post的参数要编码</param>
        /// <returns></returns>

        private string GetUrlParam(SortedList<string, string> sortedList, bool isEncode)
        {
            StringBuilder param = new StringBuilder();
            StringBuilder encodeParam = new StringBuilder();
            if (isEncode == false)
            {

                foreach (KeyValuePair<string, string> kvp in sortedList)
                {
                    string t = string.Format("{0}={1}", kvp.Key, kvp.Value);
                    param.Append(t + "&");
                }
                return param.ToString().TrimEnd('&');
            }
            else
            {
                foreach (KeyValuePair<string, string> kvp in sortedList)
                {
                    string et = string.Format("{0}={1}", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value));
                    encodeParam.Append(et + "&");
                }
                return encodeParam.ToString().TrimEnd('&');
            }

        }

        /// <summary>
        /// 获取字符串的MD5签名
        /// </summary>
        /// <param name="encode">签名时用的编码</param>
        /// <param name="param">要签名的字符串</param>
        /// <returns></returns>
        private string GetMd5Sign(string encode, string param)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] data = md5.ComputeHash(Encoding.GetEncoding(encode).GetBytes(param));
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }
            return sBuilder.ToString();

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="encode"></param>
        /// <returns></returns>
        private string PostData(string url, string data, string encode)
        {
            CookieContainer cc = new CookieContainer();
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.CookieContainer = cc;
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            Stream requestStream = request.GetRequestStream();
            byte[] byteArray = Encoding.UTF8.GetBytes(data);
            requestStream.Write(byteArray, 0, byteArray.Length);
            requestStream.Close();
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            Uri responseUri = response.ResponseUri;
            Stream receiveStream = response.GetResponseStream();
            StreamReader readStream = new StreamReader(receiveStream, System.Text.Encoding.GetEncoding(encode));
            string result = readStream.ReadToEnd();
            return result;

        }
        #endregion
    }
}
